import { useContext, useEffect, useRef } from 'react';
import { pickedProductData } from '../components/pick-input/types';
import { IAssemblyDocTreeNode } from '@amcax/base';
import { getAllKeysOfRefDoc } from '@amcax/renderer';
import { allParts } from '@app-base/pages/product-support/part-funcs';
import { useTools } from './useTools';
import { ctx2 } from '@app-base/module';
import { getAssembleNodesByIds } from '@app-base/environments/product/utils';
import { DocumentContext } from '../providers/documentProvider';

export enum ProductObjectDisplayType {
  Default = 'default',
  Hidden = 'hidden',
  Transparent = 'transparent',
}

export const useProductCommandDisplay = () => {
  const partObjectsMap = useRef<
    Record<
      string,
      {
        models: string[];
        originVisible: boolean;
      }
    >
  >({});

  const updatePartObjectsMap = (
    objects: IAssemblyDocTreeNode[],
    parentModels?: string[],
  ) => {
    let result: Record<string, { models: string[]; originVisible: boolean }> =
      {};

    objects.forEach((i) => {
      const models = parentModels ?? getAllKeysOfRefDoc(i);
      result[i.key] = {
        models,
        originVisible: i.recursivelyVisible,
      };

      if (i.type === 'product') {
        const childResult = updatePartObjectsMap(i.children, models);
        result = { ...result, ...childResult };
      }
    });

    if (parentModels == null) {
      partObjectsMap.current = { ...partObjectsMap.current, ...result };
    }

    return result;
  };

  const { rendererAssembly } = useTools();

  const updateObjectsDisplay = (
    objects: pickedProductData[],
    displayType: ProductObjectDisplayType,
  ) => {
    objects.forEach((o) => {
      if (displayType === ProductObjectDisplayType.Default) {
        setObjectOpacity(o.productId, 1);
        showObject(o.productId);
      }

      if (displayType === ProductObjectDisplayType.Hidden) {
        setObjectOpacity(o.productId, 1);
        hideObject(o.productId);
      }

      if (displayType === ProductObjectDisplayType.Transparent) {
        setObjectOpacity(o.productId, 0.6);
        restoreObjectVisibility(o.productId);
      }
    });
  };

  const addIDToObjectsMap = (id: string) => {
    if (partObjectsMap.current[id] == null) {
      const part = allParts.get(id);
      if (part != null) {
        updatePartObjectsMap([part]);
      }
    }
  };

  const setObjectVisibility = (key: string, visibility: boolean) => {
    addIDToObjectsMap(key);
    if (partObjectsMap.current[key]?.models?.length > 0) {
      rendererAssembly.setModelsVisible(
        partObjectsMap.current[key].models,
        visibility,
      );

      ctx2.viewer.requestRender();
      ctx2.viewer.sceneSetup.updatePixelBuffer();
    }
  };

  const changedObjects = useRef(new Set<string>());

  const hideObject = (key: string) => {
    changedObjects.current.add(key);
    setObjectVisibility(key, false);
  };

  const showObject = (key: string) => {
    changedObjects.current.add(key);
    setObjectVisibility(key, true);
  };

  const restoreObjectVisibility = (key: string) => {
    setObjectVisibility(key, partObjectsMap.current[key]?.originVisible);
  };

  const setObjectOpacity = (key: string, opacity: number) => {
    addIDToObjectsMap(key);
    changedObjects.current.add(key);
    if (partObjectsMap.current[key]?.models?.length > 0) {
      rendererAssembly.setModelsOpacity(
        partObjectsMap.current[key].models,
        opacity,
      );

      ctx2.viewer.requestRender();
      ctx2.viewer.sceneSetup.updatePixelBuffer();
    }
  };

  const restoreObjectDisplay = () => {
    changedObjects.current.forEach((v) => {
      restoreObjectVisibility(v);
      restoreObjectOpacity(v);
    });

    changedObjects.current.clear();
  };

  const restoreObjectOpacity = (key: string) => {
    if (partObjectsMap.current[key]?.models?.length > 0) {
      rendererAssembly.resetModelsOpacity(partObjectsMap.current[key].models);

      ctx2.viewer.requestRender();
      ctx2.viewer.sceneSetup.updatePixelBuffer();
    }
  };

  useEffect(() => () => restoreObjectDisplay(), []);

  const { docTree } = useContext(DocumentContext);

  const setModelsDisplay = (
    settings: {
      displayType: ProductObjectDisplayType;
      ids: string[];
    }[],
    parentNodes: string[],
    defaultDisplay?: ProductObjectDisplayType,
  ) => {
    const includeLeafs: IAssemblyDocTreeNode[] = [];

    const baseNodes = getAssembleNodesByIds(parentNodes, docTree);

    const appendLeafs = (nodes: IAssemblyDocTreeNode[]) => {
      nodes.forEach((n) => {
        if (
          n.children?.some((c) => ['product', 'part', 'array'].includes(c.type))
        ) {
          appendLeafs(n.children);
        } else if (n.type === 'part') {
          includeLeafs.push(n);
        }
      });
    };

    appendLeafs(baseNodes);

    const displays: Record<string, ProductObjectDisplayType> = {};

    if (defaultDisplay != null) {
      includeLeafs.forEach((n) => (displays[n.key] = defaultDisplay));
    }

    settings.forEach((setting) => {
      setting.ids.forEach((id) => {
        displays[id] = setting.displayType;
      });
    });

    Object.keys(displays).forEach((id) => {
      const display = displays[id];

      if (display === ProductObjectDisplayType.Hidden) {
        restoreObjectOpacity(id);
        hideObject(id);
      }

      if (display === ProductObjectDisplayType.Transparent) {
        restoreObjectVisibility(id);
        setObjectOpacity(id, 0.6);
      }

      if (display === ProductObjectDisplayType.Default) {
        restoreObjectOpacity(id);
        restoreObjectVisibility(id);
      }
    });
  };

  return {
    partObjectsMap,
    updatePartObjectsMap,
    updateObjectsDisplay,
    addIDToObjectsMap,
    setObjectVisibility,
    changedObjects,
    hideObject,
    showObject,
    restoreObjectDisplay,
    restoreObjectOpacity,
    restoreObjectVisibility,
    setObjectOpacity,
    setModelsDisplay,
    docTree,
  };
};
